/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#include "carina/mesh.hh"

#include "carina/common/exception.hh"

#include "carina/common/patterns.hh"
#include "carina/resources.hh"
#include "carina/common/global.hh"
#include "carina/common/containers.hh"
#include "carina/common/logging.hh"
#include "carina/utils.hh"

#include <map>
#include <sstream>
#include <stack>
#include <cassert>
#include <algorithm>

namespace Carina
{
Mesh::Mesh()
    :   m_Repeat(false),
        m_CurrentAnimation(0) {}

Mesh::Mesh(const RendererPtr& renderer, const SubmeshArray& submesh, 
           const PointDataArray& point_data,
           const IndicesArray& indices,
           const Skeleton& skel)
    :   m_VertsBuffer(renderer->createVideoBuffer(CE_VERTEX_BUFFER, point_data.size()*sizeof(float), &point_data.front(), CE_STATIC_DRAW)),
        m_IndicesBuffer(renderer->createVideoBuffer(CE_INDEX_BUFFER, indices.size()*sizeof(uint32), &indices.front(), CE_STATIC_DRAW)),
        m_Submeshes(submesh),
        m_Skeleton(skel),
        m_Repeat(false),
        m_CurrentAnimation(0) 
{
}

Mesh::~Mesh() {}

void Mesh::draw(Scene& scene, const Matrix4& mat)
{
    RendererPtr renderer = scene.getRenderer();
    if(m_Skeleton.hasBones())
    {
        DualQuatArray bones(50);

        renderer->setVideoBuffer(m_VertsBuffer, 16*sizeof(float), 0);
        if(m_CurrentAnimation)
        {
            m_AnimTimer.update();
            DualQuatArray in_pose;
            float current_time = m_AnimTimer.getGameTime()*1e-9f;

            m_CurrentAnimation->getTransform(current_time, in_pose, m_Repeat);
            m_Skeleton.skinning(in_pose, bones);
        }
        else
            m_Skeleton.getInitialSkinning(bones);
        for(size_t i = 0; i < m_Submeshes.size(); ++i)
            m_Submeshes[i].getMaterial().set("BoneWorldQuats", bones);
    }
    else
        renderer->setVideoBuffer(m_VertsBuffer, 8*sizeof(float), 0);

    renderer->setIndexBuffer(m_IndicesBuffer, IT_UINT);
    for(size_t i = 0; i < m_Submeshes.size(); ++i)
        m_Submeshes[i].draw(scene, mat);
}

bool Mesh::playAnimation(const string& name, bool repeat)
{
    DynamicArray<Animation>::iterator i = std::find(m_Animations.begin(), m_Animations.end(), name);
    if(i == m_Animations.end())
        return false;
    
    m_CurrentAnimation = &*i;
    m_AnimTimer.reset();
    m_Repeat = repeat;

    return true;
}

void Mesh::addAnimation(const Animation& anim)
{
    m_Animations.push_back(anim);
}

void Mesh::addAnimations(const AnimationArray& anims)
{
    m_Animations.insert(m_Animations.end(), anims.begin(), anims.end());
}

Submesh::Submesh(const RendererPtr& renderer, const Material& mat, unsigned off, unsigned count, bool skel)
    :   m_Offset(off),
        m_Count(count),
        m_Material(mat),
        m_InputLayout()
{
    InputElementArray arr;
    if(skel)
    {
        arr.resize(5);
        arr[3].set("BONEID", VT_RGBA32F, 8*sizeof(float));
        arr[4].set("WEIGHT", VT_RGBA32F, 12*sizeof(float));
    }
    else
        arr.resize(3);
    arr[0].set("POSITION", VT_RGB32F, 0);
    arr[1].set("NORMAL", VT_RGB32F, 3*sizeof(float));
    arr[2].set("TEXCOORD", VT_RG32F, 6*sizeof(float));
    m_InputLayout = renderer->createInputLayout(m_Material.getShaderProgram(), arr);
    m_Material.useProfile(skel ? "SkinnedMesh" : "StaticMesh");
}

Material& Submesh::getMaterial() { return m_Material; }

void Submesh::draw(const Scene& scene, const Matrix4& mat)
{
    RendererPtr renderer = scene.getRenderer();
    m_Material.setup(scene, mat);
    renderer->setInputLayout(m_InputLayout);
    renderer->drawIndexed(CE_TRIANGLE_LIST, m_Offset, m_Count);
}

void Skeleton::skinning(const DualQuatArray& in_pose, DualQuatArray& out_smat)
{
    assert(out_smat.size() >= m_BoneCount && in_pose.size() == m_Nodes.size());
    size_t current_bone;
    DualQuatArray sfmat(in_pose.size());
    std::stack<size_t> bone_stack;
    bone_stack.push(m_Root);
    sfmat[m_Root].identity();
    while(bone_stack.size())
    {
        current_bone = bone_stack.top(); bone_stack.pop();
        sfmat[current_bone] *= in_pose[current_bone];
        sfmat[current_bone].normalize();

        for(size_t i = 0, child; i < m_Nodes[current_bone].children(); ++i)
        {
            child = m_Nodes[current_bone].getChild(i);
            sfmat[child] = sfmat[current_bone];
            bone_stack.push(child);
        }
    }
    for(size_t i = 0; i < m_Nodes.size(); ++i)
        sfmat[i] *= m_Nodes[i].getInvBindTransform();
    std::copy(sfmat.begin(), sfmat.begin() + m_BoneCount, out_smat.begin());
}

// TODO: Repetitive
void Skeleton::getInitialSkinning(DualQuatArray& smat)
{
    assert(smat.size() >= m_BoneCount);
    size_t current_bone;
    DualQuatArray sfmat(m_Nodes.size());
    std::stack<size_t> bone_stack;
    bone_stack.push(m_Root);
    sfmat[m_Root].identity();
    while(bone_stack.size())
    {
        current_bone = bone_stack.top(); bone_stack.pop();
        sfmat[current_bone] *= m_Nodes[current_bone].getInitial();
        sfmat[current_bone].normalize();

        for(size_t i = 0, child; i < m_Nodes[current_bone].children(); ++i)
        {
            child = m_Nodes[current_bone].getChild(i);
            sfmat[child] = sfmat[current_bone];
            bone_stack.push(child);
        }
    }
    for(size_t i = 0; i < m_Nodes.size(); ++i)
        sfmat[i] *= m_Nodes[i].getInvBindTransform();
    std::copy(sfmat.begin(), sfmat.begin() + m_BoneCount, smat.begin());
}

Animation::Animation()
{
}

Animation::Animation(const string& name, const AnimationData& data)
    :   m_Name(name),
        m_Data(data)
{
}

Animation::Animation(const Animation& anim)
    :   m_Name(anim.m_Name),
        m_Data(anim.m_Data) 
{
}

Animation& Animation::operator=(const Animation& anim)  
{
    m_Name = anim.m_Name;
    m_Data = anim.m_Data;
    return *this; 
}

float Animation::endTime() const
{
    return m_Data.back().Time;
}

void Animation::getTransform(float t, DualQuatArray& wma, bool repeat) const
{
    size_t i, i_prev;
    if(repeat)
        t = fmod(t, endTime());
    for(i = 0;; ++i)
    {
        if(i == m_Data.size())
        {
            wma = m_Data.back().TransformQuats;
            return;
        }
        if(t < m_Data[i].Time)
            break;
    }

    float diff, dotp;
    if(i)
    {
        i_prev = i - 1;
        diff = (t - m_Data[i-1].Time)/(m_Data[i].Time - m_Data[i-1].Time); 
    }
    else
    {
        if(!repeat)
        {
            wma = m_Data.front().TransformQuats;
            return;
        }
        i_prev = m_Data.size() - 1;
        diff = t / m_Data[i].Time;
    }
    assert(0 <= diff && diff <= 1.0f);

    wma.resize(m_Data[i].TransformQuats.size());
    for(size_t j = 0; j < m_Data[i].TransformQuats.size(); ++j)
    {
        dotp = m_Data[i_prev].TransformQuats[j].non_dual.dot(m_Data[i].TransformQuats[j].non_dual);
        if(dotp < 0)
            wma[j] = interpolate(diff, m_Data[i_prev].TransformQuats[j], -m_Data[i].TransformQuats[j]);
        else
            wma[j] = interpolate(diff, m_Data[i_prev].TransformQuats[j], m_Data[i].TransformQuats[j]);
    }
}
}
